package com.project.entity;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.stereotype.Component;

import com.project.dao.annotation.Between;
import com.project.dao.annotation.In;

import javassist.CannotCompileException;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ConstPool;
import javassist.bytecode.SignatureAttribute.ClassType;
import javassist.bytecode.SignatureAttribute.MethodSignature;
import javassist.bytecode.SignatureAttribute.Type;
import javassist.bytecode.SignatureAttribute.TypeArgument;
import javassist.bytecode.annotation.Annotation;


/**
 * 根据实体类注解添加属性
 * @author MarkLau
 *
 */
@Component
public class HackModelQueryConditionBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

private static Logger logger = LoggerFactory.getLogger(HackModelQueryConditionBeanFactoryPostProcessor.class);
	
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		ClassPool pool = ClassPool.getDefault();
		try {
			pool.insertClassPath(new ClassClassPath(this.getClass()));
			CtClass cc = pool.get("com.project.entity.sys.BaseEntity");
			cc.toClass();

			for (String classname : findModelClassNames()) {
				deal(classname);
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void deal(String className) {
		boolean modified = false;
		try {
			CtClass ct = ClassPool.getDefault().get(className);
			for (CtField f : ct.getDeclaredFields()) {
				modified |= dealBetween(f, ct);
				modified |= dealIn(f, ct);
			}
			if (modified) {
				ct.toClass();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private boolean dealBetween(CtField f, CtClass ct)
			throws ClassNotFoundException, CannotCompileException {
		Between between = (Between) f.getAnnotation(Between.class);
		if (null != between) {
			// End字段
			String end = between.end();
			String suf = StringUtils.isBlank(end) ? "End" : end;
			String betweenName = f.getName() + suf;
			CtField endField = new CtField(f, f.getDeclaringClass());
			endField.setName(betweenName);
			addAnnotations(new String[]{"javax.persistence.Transient"}, endField, ct.getClassFile().getConstPool());
			ct.addField(endField);

			addGetterAndSetter(endField, ct, null);

			return true;
		}
		return false;
	}

	private boolean dealIn(CtField f, CtClass ct) throws ClassNotFoundException, CannotCompileException, NotFoundException {
		In in = (In) f.getAnnotation(In.class);
		if (null != in) {
			String suf = StringUtils.isBlank(in.value()) ? "In" : in.value();
			ClassType ctype = new ClassType("java.util.List", new TypeArgument[]{new TypeArgument(new ClassType(f.getType().getName()))});
			CtField inField = CtField.make("private java.util.List " + f.getName() + suf + ";", ct);
			inField.setGenericSignature(ctype.encode());
			addAnnotations(new String[]{"javax.persistence.Transient"}, inField, ct.getClassFile().getConstPool());
			ct.addField(inField);

			addGetterAndSetter(inField, ct, ctype);
			return true;
		}
		return false;
	}

	private void addGetterAndSetter(CtField f, CtClass ct, ClassType genericType) throws CannotCompileException {
		// 首字母大写
		String upper = f.getName().replaceFirst("" + f.getName().charAt(0), ("" + f.getName().charAt(0)).toUpperCase());
		// get方法
		CtMethod getter = CtNewMethod.getter("get" + upper, f);
		if (null != genericType) {
			getter.setGenericSignature(new MethodSignature(null,null,genericType,null).encode());
		}
		ct.addMethod(getter);
		// set方法
		CtMethod setter = CtNewMethod.setter("set" + upper, f);
		if (null != genericType) {
			setter.setGenericSignature(new MethodSignature(null,new Type[]{genericType},null,null).encode());
		}
		ct.addMethod(setter);

		logger.info("处理类型："+ct.getName()+", 增加了属性 ["+f.getName()+"] 以及 get/set 方法");
	}

	private void addAnnotations(String[] annoNames, CtField f, ConstPool cp) {
		AnnotationsAttribute aa = new AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag);
		for (String aName : annoNames) {
			Annotation a = new Annotation(aName, cp);
			aa.addAnnotation(a);
		}
		f.getFieldInfo().addAttribute(aa);
	}

	private List<String> findModelClassNames() {
    	ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false){
    		protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    			return beanDefinition.getMetadata().isIndependent();
    		}
    	};
		provider.addIncludeFilter(new AssignableTypeFilter(BaseEntity.class));

		List<String> beanClassNames = new ArrayList<String>();

		// scan in com/eling/elcms
		Set<BeanDefinition> components = provider.findCandidateComponents("com/project/entity");
		for (BeanDefinition component : components) {
			beanClassNames.add(component.getBeanClassName());
		}
		// 如果先改写子类，再改写父类，会报错：
		// java.lang.LinkageError: loader (instance of  org/apache/catalina/loader/WebappClassLoader): attempted  duplicate class definition for name: "com/eling/elcms/schedule/model/Action"
		// 正确的做法是按照先父后子的顺序排序
		Collections.sort(beanClassNames, new Comparator<String>() {
			@Override
			public int compare(String o1, String o2) {
				return getClassLevelsFromBaseModel(o1) - getClassLevelsFromBaseModel(o2);
			}
		});
		
		return beanClassNames;
	}
	
	private int getClassLevelsFromBaseModel(String className) {
		int level = 0;
		String curClassName = className;
		logger.info("当前实体："+curClassName);
		while (!curClassName.equals(BaseEntity.class.getName())) {
			try {
				if(ClassPool.getDefault()==null){
					logger.info("=====ClassPool.getDefault() is null");
					
				}else{
					if(ClassPool.getDefault().get(curClassName)==null){
						logger.info("=====ClassPool.getDefault().get(curClassName) is null");
					}else{
						if( ClassPool.getDefault().get(curClassName).getSuperclass()==null){
							logger.info("=====ClassPool.getDefault().get(curClassName).getSuperclass() is null");
						}
					}
				}
//				String curClassName1 = Class.forName(curClassName).getSuperclass().getName();
//				logger.info("=====ClassPool curClassName1: " + curClassName1);
//				curClassName = curClassName1;
				curClassName = ClassPool.getDefault().get(curClassName).getSuperclass().getName();
			} catch (Exception e) {
				e.printStackTrace();
			}
			level++;
			if(level > 100) {
				break;
			}
		}
		return level;
	}
}
